﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Vector3 = DDD.Vector3<float>;


namespace DDD {

    /// <summary>
    /// 3x3の行列
    /// </summary>
    /// <remarks>
    /// 標準的な3x3行列です。座標変換を表します。
    /// <see cref="Vector3"/>型のベクトルを<dic>V'=MV</dic>で変換可能です。 
    /// アニメート可能、エクスポート可能。
    /// </remarks>
    public struct Matrix3x3 : IExportable<float>, IAnimatable<float>, IEquatable<Matrix3x3> {


        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 行優先で指定します。
        /// </remarks>
        /// <param name="m00">M00要素</param>
        /// <param name="m01">M01要素</param>
        /// <param name="m02">M02要素</param>
        /// <param name="m10">M10要素</param>
        /// <param name="m11">M11要素</param>
        /// <param name="m12">M12要素</param>
        /// <param name="m20">M20要素</param>
        /// <param name="m21">M21要素</param>
        /// <param name="m22">M22要素</param>
        public Matrix3x3 (float m00, float m01, float m02,
                          float m10, float m11, float m12,
                          float m20, float m21, float m22)
            : this () {
            this.M00 = m00;
            this.M01 = m01;
            this.M02 = m02;
            this.M10 = m10;
            this.M11 = m11;
            this.M12 = m12;
            this.M20 = m20;
            this.M21 = m21;
            this.M22 = m22;
        }

        /// <summary>
        /// 要素を配列で指定するコンストラクター
        /// </summary>
        /// <remarks>
        /// 行優先で指定します。配列の中身はただちに内部にコピーされます。
        /// </remarks>
        /// <param name="m">9要素の配列</param>
        public Matrix3x3 (float[] m)
            : this () {
            if (m == null) {
                throw new ArgumentNullException ("Array is null");
            }
            if (m.Length < 9) {
                throw new ArgumentException ("Array is too short");
            }
            this.M00 = m[0];
            this.M01 = m[1];
            this.M02 = m[2];
            this.M10 = m[3];
            this.M11 = m[4];
            this.M12 = m[5];
            this.M20 = m[6];
            this.M21 = m[7];
            this.M22 = m[8];
        }
        #endregion

        #region Property

        /// <summary>
        /// M00要素
        /// </summary>
        public float M00 {
            get;
            private set;
        }

        /// <summary>
        /// M01要素
        /// </summary>
        public float M01 {
            get;
            private set;
        }

        /// <summary>
        /// M02要素
        /// </summary>
        public float M02 {
            get;
            private set;
        }

        /// <summary>
        /// M10要素
        /// </summary>
        public float M10 {
            get;
            private set;
        }

        /// <summary>
        /// M11要素
        /// </summary>
        public float M11 {
            get;
            private set;
        }

        /// <summary>
        /// M12要素
        /// </summary>
        public float M12 {
            get;
            private set;
        }

        /// <summary>
        /// M20要素
        /// </summary>
        public float M20 {
            get;
            private set;
        }

        /// <summary>
        /// M21要素
        /// </summary>
        public float M21 {
            get;
            private set;
        }

        /// <summary>
        /// M22要素
        /// </summary>
        public float M22 {
            get;
            private set;
        }

        ///<summary>
        ///コンポーネント数
        /// </summary>
        /// <value>コンポーネント数</value>
        public int ComponentCount {
            get { return 9; }
        }

        /// <summary>コンポーネントにアクセスするインデクサー</summary>
        /// <param name="index">インデックス</param>
        /// <returns></returns>
        public float this[int index] {
            get {
                if (index < 0 || index >= ComponentCount) {
                    throw new IndexOutOfRangeException ("Index is out of range");
                }
                switch (index) {
                    case 0: return M00;
                    case 1: return M01;
                    case 2: return M02;
                    case 3: return M10;
                    case 4: return M11;
                    case 5: return M12;
                    case 6: return M20;
                    case 7: return M21;
                    case 8: return M22;
                    default: return 0;
                }
            }
            set {
                if (index < 0 || index >= ComponentCount) {
                    throw new IndexOutOfRangeException ("Index is out of range");
                }

                switch (index) {
                    case 0: M00 = value; break;
                    case 1: M01 = value; break;
                    case 2: M02 = value; break;
                    case 3: M10 = value; break;
                    case 4: M11 = value; break;
                    case 5: M12 = value; break;
                    case 6: M20 = value; break;
                    case 7: M21 = value; break;
                    case 8: M22 = value; break;
                    default: break;
                }

            }
        }

        /// <summary>
        /// この行列の行列式
        /// </summary>
        /// <value>この行列の行列式</value>
        public float Determinant {
            get {
                return M00 * M11 * M22 - M00 * M21 * M12 + M01 * M12 * M20 - M01 * M22 * M10 + M02 * M10 * M21 - M02 * M20 * M11;
            }
        }
        #endregion

        #region Method

        /// <summary>
        ///  この行列の逆行列を作成します
        /// </summary>
        /// <remarks>
        /// 逆行列が計算できない場合(<see cref="Determinant"/>=0)例外を発生します。
        /// </remarks>
        /// <returns>新しく作成された逆行列</returns>
        public Matrix3x3 Inverse () {
            var det = Determinant;
            if (det == 0) {
                throw new InvalidOperationException ("Can't invert this matrix");
            }

            var m = new Matrix3x3 ();
            m.M00 = 1 / det * (M11 * M22 - M12 * M21);
            m.M01 = 1 / det * (M02 * M21 - M01 * M22);
            m.M02 = 1 / det * (M01 * M12 - M02 * M11);
            m.M10 = 1 / det * (M12 * M20 - M10 * M22);
            m.M11 = 1 / det * (M00 * M22 - M02 * M20);
            m.M12 = 1 / det * (M02 * M10 - M00 * M12);
            m.M20 = 1 / det * (M10 * M21 - M11 * M20);
            m.M21 = 1 / det * (M01 * M20 - M00 * M21);
            m.M22 = 1 / det * (M00 * M11 - M01 * M10);

            return m;
        }

        /// <summary>
        /// この行列の転置行列を作成します
        /// </summary>
        /// <returns>新しく作成された転置行列</returns>
        public Matrix3x3 Transpose () {
            return new Matrix3x3 (M00, M10, M20,
                                  M01, M11, M21,
                                  M02, M12, M22);
        }

        /// <summary>
        /// 3x3の単位行列を作成します
        /// </summary>
        /// <returns>新しく再生された単位行列</returns>
        public static Matrix3x3 Identity () {
            return new Matrix3x3 (1, 0, 0,
                                 0, 1, 0,
                                 0, 0, 1);
        }

        /// <summary>
        /// 全要素をfloatの配列にキャストします
        /// </summary>
        /// <param name="m">変換元の3x3行列</param>
        /// <returns>変換後のfloatの配列</returns>
        public static explicit operator float[] (Matrix3x3 m) {
            return new float[] { m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8] };
        }


        /// <summary>
        /// この3x3行列を使って<see cref="Vector3"/>型のベクトルを変換します。<dic>V'=MV</dic>.
        /// </summary>
        /// <param name="m">3x3行列</param>
        /// <param name="v">ベクトル</param>
        /// <returns>変換後のベクトル</returns>
        public static Vector3 operator * (Matrix3x3 m, Vector3 v) {
            return new Vector3 (m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
                                m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
                                m[6] * v[0] + m[7] * v[1] + m[8] * v[2]);
        }


        /// <summary>
        /// 同値性を比較します
        /// </summary>
        /// <remarks>
        /// 同値性を比較する汎用の比較関数です。
        /// <see cref="Matrix3x3"/>オブジェクトはコンポーネント要素が厳密に一致しなくても、
        /// 全てのコンポーネント要素で差が<see cref="GlobalSettings.Tolerance"/>以下であれば等しいと見なされます。
        /// </remarks>
        /// <param name="obj">右辺のオブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public override bool Equals (object obj) {
            if (object.ReferenceEquals (obj, null)) {
                return false;
            }
            if (object.ReferenceEquals (obj, this)) {
                return true;
            }
            var m = obj as Matrix3x3?;
            if (m == null) {
                return false;
            }

            return this.Equals (m.Value);
        }

        /// <summary>
        /// タイプセーフな同値性を比較します
        /// </summary>
        /// <remarks>
        /// 
        /// </remarks>
        /// <param name="m">右辺の<see cref="Matrix3x3"/>オブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public bool Equals (Matrix3x3 m) {
            for (int i = 0; i < ComponentCount; i++) {
                if (Math.Abs (this[i] - m[i]) >= GlobalSettings.Tolerance) {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public static bool operator == (Matrix3x3 left, Matrix3x3 right) {
            return left.Equals (right);
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しくないときtrue, そうでないときfalse</returns>
        public static bool operator != (Matrix3x3 left, Matrix3x3 right) {
            return !(left == right);
        }

        /// <summary>
        /// 一意のハッシュ値を取得します
        /// </summary>
        /// <remarks>
        /// このメソッドは同値性の比較メソッドと異なり
        /// <see cref="GlobalSettings.Tolerance"/>の影響を受けません。
        /// </remarks>
        /// <returns></returns>
        public override int GetHashCode () {
            return base.GetHashCode ();
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary> 
        public override string ToString () {
            return string.Format ("Matrix3x3 : M={0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}", M00, M01, M02, M10, M11, M12, M20, M21, M22);
        }

        #endregion
    }
}
